using System;
using System.Collections.Generic;
using Storage.Core;

namespace Storage.Server
{
	internal class ScopeBasedStorage : IStorage
	{
		public ScopeBasedStorage(Dictionary<Guid, PlainEntity> entities, Dictionary<string, Scope> scopes, IStorageOperationLog operationLog)
		{
			this.entities = entities;
			this.scopes = scopes;
			this.operationLog = operationLog;
			syncObject = new object();
		}

		public ScopeBasedStorage(IStorageOperationLog operationLog)
			: this (new Dictionary<Guid, PlainEntity>(), new Dictionary<string, Scope>(), operationLog) { }

		public bool AddOrUpdate(PlainEntity newEntity)
		{
			Guid id = newEntity.Id;
			lock (syncObject)
			{
				operationLog.LogAddOrUpdateEntity(newEntity);

				Scope scope;
				if (!scopes.TryGetValue(newEntity.Type, out scope))
				{
					scope = new Scope();
					scope.AddOrUpdate(newEntity);
					scopes.Add(newEntity.Type, scope);
				}
				else scope.AddOrUpdate(newEntity);

				PlainEntity currentEntity;
				if (!entities.TryGetValue(id, out currentEntity))
				{
					entities.Add(id, newEntity);
					return true;
				}

				// (iloktionov): If update changes entity type, we need to cleanup a scope with old type.
				if (!currentEntity.Type.Equals(newEntity.Type))
				{
					Scope oldScope = scopes[currentEntity.Type];
					oldScope.Remove(id);
					if (oldScope.Count <= 0)
						scopes.Remove(currentEntity.Type);
				}

				entities[id] = newEntity;
				return false;
			}
		}

		public bool Delete(Guid id)
		{
			lock (syncObject)
			{
				PlainEntity entity;
				if (!entities.TryGetValue(id, out entity))
					return false;

				operationLog.LogDeleteEntity(id);
				entities.Remove(id);

				var scope = scopes[entity.Type];
				scope.Remove(id);
				if (scope.Count <= 0)
					scopes.Remove(entity.Type);
				return true;
			}
		}

		public PlainEntity ReadById(Guid id)
		{
			lock (syncObject)
				return entities.ContainsKey(id) ? entities[id] : null;
		}

		public IList<PlainEntity> ReadByType(string type)
		{
			lock (syncObject)
                return scopes.ContainsKey(type) ? scopes[type].ToList() : new List<PlainEntity>();
		}

		public void Dispose()
		{
			operationLog.Dispose();
		}

		private readonly IStorageOperationLog operationLog;
		private readonly Dictionary<Guid,PlainEntity> entities;
	    private readonly Dictionary<string, Scope> scopes; 
		private readonly object syncObject;
	}
}